/*
 * 版权所有 (C) 2001-2014 深圳市艾派应用系统有限公司。保留所有权利。
 * 版本：
 * 修改记录：
 *      1、2014-7-29，tfl创建。 
 */
package com.cmcc.aregister.ui.view;

import com.cmcc.aregister.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;

public class MultiDirectionSlidingDrawer extends ViewGroup
{

    public static final int ORIENTATION_RTL = 0;
    public static final int ORIENTATION_BTT = 1;
    public static final int ORIENTATION_LTR = 2;
    public static final int ORIENTATION_TTB = 3;

    private static final int TAP_THRESHOLD = 6;
    private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
    private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
    private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
    private static final float MAXIMUM_ACCELERATION = 2000.0f;
    private static final int VELOCITY_UNITS = 1000;
    private static final int MSG_ANIMATE = 1000;
    private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

    private static final int EXPANDED_FULL_OPEN = -10001;
    private static final int COLLAPSED_FULL_CLOSED = -10002;

    private final int mHandleId;
    private final int mContentId;

    private View mHandle;
    private View mContent;

    private final Rect mFrame = new Rect();
    private final Rect mInvalidate = new Rect();
    private boolean mTracking;
    private boolean mLocked;

    private VelocityTracker mVelocityTracker;

    private boolean mInvert;
    private boolean mVertical;
    private boolean mExpanded;
    private int mBottomOffset;
    private int mTopOffset;
    private int mHandleHeight;
    private int mHandleWidth;

    private OnDrawerOpenListener mOnDrawerOpenListener;
    private OnDrawerCloseListener mOnDrawerCloseListener;
    private OnDrawerScrollListener mOnDrawerScrollListener;

    private final Handler mHandler = new SlidingHandler();
    private float mAnimatedAcceleration;
    private float mAnimatedVelocity;
    private float mAnimationPosition;
    private long mAnimationLastTime;
    private long mCurrentAnimationTime;
    private int mTouchDelta;
    private boolean mAnimating;
    private boolean mAllowSingleTap;
    private boolean mAnimateOnClick;

    private final int mTapThreshold;
    private final int mMaximumTapVelocity;
    private int mMaximumMinorVelocity;
    private int mMaximumMajorVelocity;
    private int mMaximumAcceleration;
    private final int mVelocityUnits;

    /**
     * Callback invoked when the drawer is opened.
     */
    public static interface OnDrawerOpenListener
    {

        /**
         * Invoked when the drawer becomes fully open.
         */
        public void onDrawerOpened();
    }

    /**
     * Callback invoked when the drawer is closed.
     */
    public static interface OnDrawerCloseListener
    {

        /**
         * Invoked when the drawer becomes fully closed.
         */
        public void onDrawerClosed();
    }

    /**
     * Callback invoked when the drawer is scrolled.
     */
    public static interface OnDrawerScrollListener
    {

        /**
         * Invoked when the user starts dragging/flinging the drawer's handle.
         */
        public void onScrollStarted();

        /**
         * Invoked when the user stops dragging/flinging the drawer's handle.
         */
        public void onScrollEnded();
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in
     * XML.
     * 
     * @param context The application's environment.
     * @param attrs The attributes defined in XML.
     */
    public MultiDirectionSlidingDrawer(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in
     * XML.
     * 
     * @param context The application's environment.
     * @param attrs The attributes defined in XML.
     * @param defStyle The style to apply to this widget.
     */
    public MultiDirectionSlidingDrawer(Context context, AttributeSet attrs,
        int defStyle)
    {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.MultiDirectionSlidingDrawer, defStyle, 0);

        int orientation = a.getInt(
            R.styleable.MultiDirectionSlidingDrawer_direction, ORIENTATION_BTT);
        mVertical = (orientation == ORIENTATION_BTT || orientation == ORIENTATION_TTB);
        mBottomOffset = (int) a.getDimension(
            R.styleable.MultiDirectionSlidingDrawer_bottomOffset, 0.0f);
        mTopOffset = (int) a.getDimension(
            R.styleable.MultiDirectionSlidingDrawer_topOffset, 0.0f);
        mAllowSingleTap = a.getBoolean(
            R.styleable.MultiDirectionSlidingDrawer_allowSingleTap, true);
        mAnimateOnClick = a.getBoolean(
            R.styleable.MultiDirectionSlidingDrawer_animateOnClick, true);
        mInvert = (orientation == ORIENTATION_TTB || orientation == ORIENTATION_LTR);

        int handleId = a.getResourceId(
            R.styleable.MultiDirectionSlidingDrawer_handle, 0);
        if (handleId == 0)
        {
            throw new IllegalArgumentException(
                "The handle attribute is required and must refer "
                    + "to a valid child.");
        }

        int contentId = a.getResourceId(
            R.styleable.MultiDirectionSlidingDrawer_content, 0);
        if (contentId == 0)
        {
            throw new IllegalArgumentException(
                "The content attribute is required and must refer "
                    + "to a valid child.");
        }

        if (handleId == contentId)
        {
            throw new IllegalArgumentException(
                "The content and handle attributes must refer "
                    + "to different children.");
        }
        mHandleId = handleId;
        mContentId = contentId;

        final float density = getResources().getDisplayMetrics().density;
        mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
        mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
        mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
        mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
        mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
        mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);

        if (mInvert)
        {
            mMaximumAcceleration = -mMaximumAcceleration;
            mMaximumMajorVelocity = -mMaximumMajorVelocity;
            mMaximumMinorVelocity = -mMaximumMinorVelocity;
        }

        a.recycle();
        setAlwaysDrawnWithCacheEnabled(false);
    }

    @Override
    protected void onFinishInflate()
    {
        mHandle = findViewById(mHandleId);
        if (mHandle == null)
        {
            throw new IllegalArgumentException(
                "The handle attribute is must refer to an" + " existing child.");
        }
        mHandle.setOnClickListener(new DrawerToggler());

        mContent = findViewById(mContentId);
        if (mContent == null)
        {
            throw new IllegalArgumentException(
                "The content attribute is must refer to an"
                    + " existing child.");
        }
        mContent.setVisibility(View.GONE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.UNSPECIFIED
            || heightSpecMode == MeasureSpec.UNSPECIFIED)
        {
            throw new RuntimeException(
                "SlidingDrawer cannot have UNSPECIFIED dimensions");
        }

        final View handle = mHandle;
        measureChild(handle, widthMeasureSpec, heightMeasureSpec);

        if (mVertical)
        {
            int height = heightSpecSize - mTopOffset;
            mContent
                .measure(MeasureSpec.makeMeasureSpec(widthSpecSize,
                    MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
                    MeasureSpec.EXACTLY));
        }
        else
        {
            int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
            mContent.measure(MeasureSpec.makeMeasureSpec(width,
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                heightSpecSize, MeasureSpec.EXACTLY));
        }

        setMeasuredDimension(widthSpecSize, heightSpecSize);
    }

    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        final long drawingTime = getDrawingTime();
        final View handle = mHandle;
        final boolean isVertical = mVertical;

        drawChild(canvas, handle, drawingTime);

        if (mTracking || mAnimating)
        {
            final Bitmap cache = mContent.getDrawingCache();
            if (cache != null)
            {
                if (isVertical)
                {
                    if (mInvert)
                    {
                        canvas.drawBitmap(cache, 0, handle.getTop()
                            - (getBottom() - getTop()) + mHandleHeight, null);
                    }
                    else
                    {
                        canvas.drawBitmap(cache, 0, handle.getBottom(), null);
                    }
                }
                else
                {
                    canvas.drawBitmap(
                        cache,
                        mInvert ? handle.getLeft() - cache.getWidth() : handle
                            .getRight(), 0, null);
                }
            }
            else
            {
                canvas.save();
                if (mInvert)
                {
                    canvas.translate(
                        isVertical ? 0 : handle.getLeft() - mTopOffset
                            - mContent.getMeasuredWidth(),
                        isVertical ? handle.getTop() - mTopOffset
                            - mContent.getMeasuredHeight() : 0);
                }
                else
                {
                    canvas.translate(isVertical ? 0 : handle.getLeft()
                        - mTopOffset, isVertical ? handle.getTop() - mTopOffset
                        : 0);
                }
                drawChild(canvas, mContent, drawingTime);
                canvas.restore();
            }
            invalidate();
        }
        else if (mExpanded)
        {
            drawChild(canvas, mContent, drawingTime);
        }
    }

    public static final String LOG_TAG = "Sliding";

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (mTracking)
        {
            return;
        }

        final int width = r - l;
        final int height = b - t;

        final View handle = mHandle;

        int handleWidth = handle.getMeasuredWidth();
        int handleHeight = handle.getMeasuredHeight();

        Log.d(LOG_TAG, "handleHeight: " + handleHeight);

        int handleLeft;
        int handleTop;

        final View content = mContent;

        if (mVertical)
        {
            handleLeft = (width - handleWidth) / 2;
            if (mInvert)
            {
                Log.d(LOG_TAG, "content.layout(1)");
                handleTop = mExpanded ? height - mBottomOffset - handleHeight
                    : mTopOffset;
                content.layout(0, mTopOffset, content.getMeasuredWidth(),
                    mTopOffset + content.getMeasuredHeight());
            }
            else
            {
                handleTop = mExpanded ? mTopOffset : height - handleHeight
                    + mBottomOffset;
                content.layout(0, mTopOffset + handleHeight,
                    content.getMeasuredWidth(), mTopOffset + handleHeight
                        + content.getMeasuredHeight());
            }
        }
        else
        {
            handleTop = (height - handleHeight) / 2;
            if (mInvert)
            {
                handleLeft = mExpanded ? width - mBottomOffset - handleWidth
                    : mTopOffset;
                content.layout(mTopOffset, 0,
                    mTopOffset + content.getMeasuredWidth(),
                    content.getMeasuredHeight());
            }
            else
            {
                handleLeft = mExpanded ? mTopOffset : width - handleWidth
                    + mBottomOffset;
                content.layout(mTopOffset + handleWidth, 0, mTopOffset
                    + handleWidth + content.getMeasuredWidth(),
                    content.getMeasuredHeight());
            }
        }

        handle.layout(handleLeft, handleTop, handleLeft + handleWidth,
            handleTop + handleHeight);
        mHandleHeight = handle.getHeight();
        mHandleWidth = handle.getWidth();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event)
    {
        if (mLocked)
        {
            return false;
        }

        final int action = event.getAction();

        float x = event.getX();
        float y = event.getY();

        final Rect frame = mFrame;
        final View handle = mHandle;

        handle.getHitRect(frame);
        if (!mTracking && !frame.contains((int) x, (int) y))
        {
            return false;
        }

        if (action == MotionEvent.ACTION_DOWN)
        {
            mTracking = true;

            handle.setPressed(true);
            // Must be called before prepareTracking()
            prepareContent();

            // Must be called after prepareContent()
            if (mOnDrawerScrollListener != null)
            {
                mOnDrawerScrollListener.onScrollStarted();
            }

            if (mVertical)
            {
                final int top = mHandle.getTop();
                mTouchDelta = (int) y - top;
                prepareTracking(top);
            }
            else
            {
                final int left = mHandle.getLeft();
                mTouchDelta = (int) x - left;
                prepareTracking(left);
            }
            mVelocityTracker.addMovement(event);
        }

        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        if (mLocked)
        {
            return true;
        }

        if (mTracking)
        {
            mVelocityTracker.addMovement(event);
            final int action = event.getAction();
            switch (action)
            {
            case MotionEvent.ACTION_MOVE:
                moveHandle((int) (mVertical ? event.getY() : event.getX())
                    - mTouchDelta);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(mVelocityUnits);

                float yVelocity = velocityTracker.getYVelocity();
                float xVelocity = velocityTracker.getXVelocity();
                boolean negative;

                final boolean vertical = mVertical;
                if (vertical)
                {
                    negative = yVelocity < 0;
                    if (xVelocity < 0)
                    {
                        xVelocity = -xVelocity;
                    }
                    // fix by Maciej Ciemięga.
                    if ((!mInvert && xVelocity > mMaximumMinorVelocity)
                        || (mInvert && xVelocity < mMaximumMinorVelocity))
                    {
                        xVelocity = mMaximumMinorVelocity;
                    }
                }
                else
                {
                    negative = xVelocity < 0;
                    if (yVelocity < 0)
                    {
                        yVelocity = -yVelocity;
                    }
                    // fix by Maciej Ciemięga.
                    if ((!mInvert && yVelocity > mMaximumMinorVelocity)
                        || (mInvert && yVelocity < mMaximumMinorVelocity))
                    {
                        yVelocity = mMaximumMinorVelocity;
                    }
                }

                float velocity = (float) Math.hypot(xVelocity, yVelocity);
                if (negative)
                {
                    velocity = -velocity;
                }

                final int handleTop = mHandle.getTop();
                final int handleLeft = mHandle.getLeft();
                final int handleBottom = mHandle.getBottom();
                final int handleRight = mHandle.getRight();

                if (Math.abs(velocity) < mMaximumTapVelocity)
                {
                    boolean c1;
                    boolean c2;
                    boolean c3;
                    boolean c4;

                    if (mInvert)
                    {
                        c1 = (mExpanded && (getBottom() - handleBottom) < mTapThreshold
                            + mBottomOffset);
                        c2 = (!mExpanded && handleTop < mTopOffset
                            + mHandleHeight - mTapThreshold);
                        c3 = (mExpanded && (getRight() - handleRight) < mTapThreshold
                            + mBottomOffset);
                        c4 = (!mExpanded && handleLeft > mTopOffset
                            + mHandleWidth + mTapThreshold);
                    }
                    else
                    {
                        c1 = (mExpanded && handleTop < mTapThreshold
                            + mTopOffset);
                        c2 = (!mExpanded && handleTop > mBottomOffset
                            + getBottom() - getTop() - mHandleHeight
                            - mTapThreshold);
                        c3 = (mExpanded && handleLeft < mTapThreshold
                            + mTopOffset);
                        c4 = (!mExpanded && handleLeft > mBottomOffset
                            + getRight() - getLeft() - mHandleWidth
                            - mTapThreshold);
                    }

                    Log.d(LOG_TAG, "ACTION_UP: " + "c1: " + c1 + ", c2: " + c2
                        + ", c3: " + c3 + ", c4: " + c4);

                    if (vertical ? c1 || c2 : c3 || c4)
                    {

                        if (mAllowSingleTap)
                        {
                            playSoundEffect(SoundEffectConstants.CLICK);

                            if (mExpanded)
                            {
                                animateClose(vertical ? handleTop : handleLeft);
                            }
                            else
                            {
                                animateOpen(vertical ? handleTop : handleLeft);
                            }
                        }
                        else
                        {
                            performFling(vertical ? handleTop : handleLeft,
                                velocity, false);
                        }
                    }
                    else
                    {
                        performFling(vertical ? handleTop : handleLeft,
                            velocity, false);
                    }
                }
                else
                {
                    performFling(vertical ? handleTop : handleLeft, velocity,
                        false);
                }
            }
                break;
            }
        }

        return mTracking || mAnimating || super.onTouchEvent(event);
    }

    private void animateClose(int position)
    {
        prepareTracking(position);
        performFling(position, mMaximumAcceleration, true);
    }

    private void animateOpen(int position)
    {
        prepareTracking(position);
        performFling(position, -mMaximumAcceleration, true);
    }

    private void performFling(int position, float velocity, boolean always)
    {
        mAnimationPosition = position;
        mAnimatedVelocity = velocity;

        boolean c1;
        boolean c2;
        boolean c3;

        if (mExpanded)
        {
            int bottom = mVertical ? getBottom() : getRight();
            int handleHeight = mVertical ? mHandleHeight : mHandleWidth;

            Log.d(LOG_TAG, "position: " + position + ", velocity: " + velocity
                + ", mMaximumMajorVelocity: " + mMaximumMajorVelocity);
            c1 = mInvert ? velocity < mMaximumMajorVelocity
                : velocity > mMaximumMajorVelocity;
            c2 = mInvert ? (bottom - (position + handleHeight)) + mBottomOffset > handleHeight
                : position > mTopOffset
                    + (mVertical ? mHandleHeight : mHandleWidth);
            c3 = mInvert ? velocity < -mMaximumMajorVelocity
                : velocity > -mMaximumMajorVelocity;
            Log.d(LOG_TAG, "EXPANDED. c1: " + c1 + ", c2: " + c2 + ", c3: "
                + c3);
            if (always || (c1 || (c2 && c3)))
            {
                // We are expanded, So animate to CLOSE!
                mAnimatedAcceleration = mMaximumAcceleration;
                if (mInvert)
                {
                    if (velocity > 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
                else
                {
                    if (velocity < 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
            }
            else
            {
                // We are expanded, but they didn't move sufficiently to cause
                // us to retract. Animate back to the expanded position. so
                // animate BACK to expanded!
                mAnimatedAcceleration = -mMaximumAcceleration;

                if (mInvert)
                {
                    if (velocity < 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
                else
                {
                    if (velocity > 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
            }
        }
        else
        {

            // WE'RE COLLAPSED

            c1 = mInvert ? velocity < mMaximumMajorVelocity
                : velocity > mMaximumMajorVelocity;
            c2 = mInvert ? (position < (mVertical ? getHeight() : getWidth()) / 2)
                : (position > (mVertical ? getHeight() : getWidth()) / 2);
            c3 = mInvert ? velocity < -mMaximumMajorVelocity
                : velocity > -mMaximumMajorVelocity;

            Log.d(LOG_TAG, "COLLAPSED. position: " + position + ", velocity: "
                + velocity + ", mMaximumMajorVelocity: "
                + mMaximumMajorVelocity);
            Log.d(LOG_TAG, "COLLAPSED. always: " + always + ", c1: " + c1
                + ", c2: " + c2 + ", c3: " + c3);

            if (!always && (c1 || (c2 && c3)))
            {
                mAnimatedAcceleration = mMaximumAcceleration;

                if (mInvert)
                {
                    if (velocity > 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
                else
                {
                    if (velocity < 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
            }
            else
            {
                mAnimatedAcceleration = -mMaximumAcceleration;

                if (mInvert)
                {
                    if (velocity < 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
                else
                {
                    if (velocity > 0)
                    {
                        mAnimatedVelocity = 0;
                    }
                }
            }
        }

        long now = SystemClock.uptimeMillis();
        mAnimationLastTime = now;
        mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
        mAnimating = true;
        mHandler.removeMessages(MSG_ANIMATE);
        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
            mCurrentAnimationTime);
        stopTracking();
    }

    private void prepareTracking(int position)
    {
        mTracking = true;
        mVelocityTracker = VelocityTracker.obtain();
        boolean opening = !mExpanded;

        if (opening)
        {
            mAnimatedAcceleration = mMaximumAcceleration;
            mAnimatedVelocity = mMaximumMajorVelocity;
            if (mInvert)
                mAnimationPosition = mTopOffset;
            else
                mAnimationPosition = mBottomOffset
                    + (mVertical ? getHeight() - mHandleHeight : getWidth()
                        - mHandleWidth);
            moveHandle((int) mAnimationPosition);
            mAnimating = true;
            mHandler.removeMessages(MSG_ANIMATE);
            long now = SystemClock.uptimeMillis();
            mAnimationLastTime = now;
            mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
            mAnimating = true;
        }
        else
        {
            if (mAnimating)
            {
                mAnimating = false;
                mHandler.removeMessages(MSG_ANIMATE);
            }
            moveHandle(position);
        }
    }

    private void moveHandle(int position)
    {
        final View handle = mHandle;

        if (mVertical)
        {
            if (position == EXPANDED_FULL_OPEN)
            {
                if (mInvert)
                    handle.offsetTopAndBottom(mBottomOffset + getBottom()
                        - getTop() - mHandleHeight);
                else
                    handle.offsetTopAndBottom(mTopOffset - handle.getTop());
                invalidate();
            }
            else if (position == COLLAPSED_FULL_CLOSED)
            {
                if (mInvert)
                {
                    handle.offsetTopAndBottom(mTopOffset - handle.getTop());
                }
                else
                {
                    handle.offsetTopAndBottom(mBottomOffset + getBottom()
                        - getTop() - mHandleHeight - handle.getTop());
                }
                invalidate();
            }
            else
            {
                final int top = handle.getTop();
                int deltaY = position - top;
                if (position < mTopOffset)
                {
                    deltaY = mTopOffset - top;
                }
                else if (deltaY > mBottomOffset + getBottom() - getTop()
                    - mHandleHeight - top)
                {
                    deltaY = mBottomOffset + getBottom() - getTop()
                        - mHandleHeight - top;
                }

                handle.offsetTopAndBottom(deltaY);

                final Rect frame = mFrame;
                final Rect region = mInvalidate;

                handle.getHitRect(frame);
                region.set(frame);

                region.union(frame.left, frame.top - deltaY, frame.right,
                    frame.bottom - deltaY);
                region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom
                    - deltaY + mContent.getHeight());

                invalidate(region);
            }
        }
        else
        {
            if (position == EXPANDED_FULL_OPEN)
            {
                if (mInvert)
                    handle.offsetLeftAndRight(mBottomOffset + getRight()
                        - getLeft() - mHandleWidth);
                else
                    handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
                invalidate();
            }
            else if (position == COLLAPSED_FULL_CLOSED)
            {
                if (mInvert)
                    handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
                else
                    handle.offsetLeftAndRight(mBottomOffset + getRight()
                        - getLeft() - mHandleWidth - handle.getLeft());
                invalidate();
            }
            else
            {
                final int left = handle.getLeft();
                int deltaX = position - left;
                if (position < mTopOffset)
                {
                    deltaX = mTopOffset - left;
                }
                else if (deltaX > mBottomOffset + getRight() - getLeft()
                    - mHandleWidth - left)
                {
                    deltaX = mBottomOffset + getRight() - getLeft()
                        - mHandleWidth - left;
                }
                handle.offsetLeftAndRight(deltaX);

                final Rect frame = mFrame;
                final Rect region = mInvalidate;

                handle.getHitRect(frame);
                region.set(frame);

                region.union(frame.left - deltaX, frame.top, frame.right
                    - deltaX, frame.bottom);
                region.union(frame.right - deltaX, 0, frame.right - deltaX
                    + mContent.getWidth(), getHeight());

                invalidate(region);
            }
        }
    }

    private void prepareContent()
    {
        if (mAnimating)
        {
            return;
        }

        // Something changed in the content, we need to honor the layout request
        // before creating the cached bitmap
        final View content = mContent;
        if (content.isLayoutRequested())
        {

            if (mVertical)
            {
                final int handleHeight = mHandleHeight;
                int height = getBottom() - getTop() - handleHeight - mTopOffset;
                content.measure(MeasureSpec.makeMeasureSpec(getRight()
                    - getLeft(), MeasureSpec.EXACTLY), MeasureSpec
                    .makeMeasureSpec(height, MeasureSpec.EXACTLY));

                Log.d(LOG_TAG, "content.layout(2)");

                if (mInvert)
                    content.layout(0, mTopOffset, content.getMeasuredWidth(),
                        mTopOffset + content.getMeasuredHeight());
                else
                    content.layout(0, mTopOffset + handleHeight,
                        content.getMeasuredWidth(), mTopOffset + handleHeight
                            + content.getMeasuredHeight());

            }
            else
            {

                final int handleWidth = mHandle.getWidth();
                int width = getRight() - getLeft() - handleWidth - mTopOffset;
                content.measure(MeasureSpec.makeMeasureSpec(width,
                    MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                    getBottom() - getTop(), MeasureSpec.EXACTLY));

                if (mInvert)
                    content.layout(mTopOffset, 0,
                        mTopOffset + content.getMeasuredWidth(),
                        content.getMeasuredHeight());
                else
                    content.layout(handleWidth + mTopOffset, 0, mTopOffset
                        + handleWidth + content.getMeasuredWidth(),
                        content.getMeasuredHeight());
            }
        }
        // Try only once... we should really loop but it's not a big deal
        // if the draw was cancelled, it will only be temporary anyway
        content.getViewTreeObserver().dispatchOnPreDraw();
        content.buildDrawingCache();

        content.setVisibility(View.GONE);
    }

    private void stopTracking()
    {
        mHandle.setPressed(false);
        mTracking = false;

        if (mOnDrawerScrollListener != null)
        {
            mOnDrawerScrollListener.onScrollEnded();
        }

        if (mVelocityTracker != null)
        {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void doAnimation()
    {
        if (mAnimating)
        {
            incrementAnimation();

            if (mInvert)
            {
                if (mAnimationPosition < mTopOffset)
                {
                    mAnimating = false;
                    closeDrawer();
                }
                else if (mAnimationPosition >= mTopOffset
                    + (mVertical ? getHeight() : getWidth()) - 1)
                {
                    mAnimating = false;
                    openDrawer();
                }
                else
                {
                    moveHandle((int) mAnimationPosition);
                    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                    mHandler.sendMessageAtTime(
                        mHandler.obtainMessage(MSG_ANIMATE),
                        mCurrentAnimationTime);
                }
            }
            else
            {
                if (mAnimationPosition >= mBottomOffset
                    + (mVertical ? getHeight() : getWidth()) - 1)
                {
                    mAnimating = false;
                    closeDrawer();
                }
                else if (mAnimationPosition < mTopOffset)
                {
                    mAnimating = false;
                    openDrawer();
                }
                else
                {
                    moveHandle((int) mAnimationPosition);
                    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                    mHandler.sendMessageAtTime(
                        mHandler.obtainMessage(MSG_ANIMATE),
                        mCurrentAnimationTime);
                }
            }
        }
    }

    private void incrementAnimation()
    {
        long now = SystemClock.uptimeMillis();
        float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
        final float position = mAnimationPosition;
        final float v = mAnimatedVelocity; // px/s
        final float a = mInvert ? mAnimatedAcceleration : mAnimatedAcceleration; // px/s/s
        mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px
        mAnimatedVelocity = v + (a * t); // px/s
        mAnimationLastTime = now; // ms
    }

    /**
     * Toggles the drawer open and close. Takes effect immediately.
     * 
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #animateToggle()
     */
    public void toggle()
    {
        if (!mExpanded)
        {
            openDrawer();
        }
        else
        {
            closeDrawer();
        }
        invalidate();
        requestLayout();
    }

    /**
     * Toggles the drawer open and close with an animation.
     * 
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #toggle()
     */
    public void animateToggle()
    {
        if (!mExpanded)
        {
            animateOpen();
        }
        else
        {
            animateClose();
        }
    }

    /**
     * Opens the drawer immediately.
     * 
     * @see #toggle()
     * @see #close()
     * @see #animateOpen()
     */
    public void open()
    {
        openDrawer();
        invalidate();
        requestLayout();

        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    }

    /**
     * Closes the drawer immediately.
     * 
     * @see #toggle()
     * @see #open()
     * @see #animateClose()
     */
    public void close()
    {
        closeDrawer();
        invalidate();
        requestLayout();
    }

    /**
     * Closes the drawer with an animation.
     * 
     * @see #close()
     * @see #open()
     * @see #animateOpen()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateClose()
    {
        prepareContent();
        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
        if (scrollListener != null)
        {
            scrollListener.onScrollStarted();
        }
        animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());

        if (scrollListener != null)
        {
            scrollListener.onScrollEnded();
        }
    }

    /**
     * Opens the drawer with an animation.
     * 
     * @see #close()
     * @see #open()
     * @see #animateClose()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateOpen()
    {
        prepareContent();
        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
        if (scrollListener != null)
        {
            scrollListener.onScrollStarted();
        }
        animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());

        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);

        if (scrollListener != null)
        {
            scrollListener.onScrollEnded();
        }
    }

    private void closeDrawer()
    {
        moveHandle(COLLAPSED_FULL_CLOSED);
        mContent.setVisibility(View.GONE);
        mContent.destroyDrawingCache();

        if (!mExpanded)
        {
            return;
        }

        mExpanded = false;
        if (mOnDrawerCloseListener != null)
        {
            mOnDrawerCloseListener.onDrawerClosed();
        }
    }

    private void openDrawer()
    {
        moveHandle(EXPANDED_FULL_OPEN);
        mContent.setVisibility(View.VISIBLE);

        if (mExpanded)
        {
            return;
        }

        mExpanded = true;

        if (mOnDrawerOpenListener != null)
        {
            mOnDrawerOpenListener.onDrawerOpened();
        }
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes
     * open.
     * 
     * @param onDrawerOpenListener The listener to be notified when the drawer
     *        is opened.
     */
    public void setOnDrawerOpenListener(
        OnDrawerOpenListener onDrawerOpenListener)
    {
        mOnDrawerOpenListener = onDrawerOpenListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes
     * close.
     * 
     * @param onDrawerCloseListener The listener to be notified when the drawer
     *        is closed.
     */
    public void setOnDrawerCloseListener(
        OnDrawerCloseListener onDrawerCloseListener)
    {
        mOnDrawerCloseListener = onDrawerCloseListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer starts or
     * ends a scroll. A fling is considered as a scroll. A fling will also
     * trigger a drawer opened or drawer closed event.
     * 
     * @param onDrawerScrollListener The listener to be notified when scrolling
     *        starts or stops.
     */
    public void setOnDrawerScrollListener(
        OnDrawerScrollListener onDrawerScrollListener)
    {
        mOnDrawerScrollListener = onDrawerScrollListener;
    }

    /**
     * Returns the handle of the drawer.
     * 
     * @return The View reprenseting the handle of the drawer, identified by the
     *         "handle" id in XML.
     */
    public View getHandle()
    {
        return mHandle;
    }

    /**
     * Returns the content of the drawer.
     * 
     * @return The View reprenseting the content of the drawer, identified by
     *         the "content" id in XML.
     */
    public View getContent()
    {
        return mContent;
    }

    /**
     * Unlocks the SlidingDrawer so that touch events are processed.
     * 
     * @see #lock()
     */
    public void unlock()
    {
        mLocked = false;
    }

    /**
     * Locks the SlidingDrawer so that touch events are ignores.
     * 
     * @see #unlock()
     */
    public void lock()
    {
        mLocked = true;
    }

    /**
     * Indicates whether the drawer is currently fully opened.
     * 
     * @return True if the drawer is opened, false otherwise.
     */
    public boolean isOpened()
    {
        return mExpanded;
    }

    /**
     * Indicates whether the drawer is scrolling or flinging.
     * 
     * @return True if the drawer is scroller or flinging, false otherwise.
     */
    public boolean isMoving()
    {
        return mTracking || mAnimating;
    }

    private class DrawerToggler implements OnClickListener
    {

        public void onClick(View v)
        {
            if (mLocked)
            {
                return;
            }
            // mAllowSingleTap isn't relevant here; you're *always*
            // allowed to open/close the drawer by clicking with the
            // trackball.

            if (mAnimateOnClick)
            {
                animateToggle();
            }
            else
            {
                toggle();
            }
        }
    }

    private class SlidingHandler extends Handler
    {

        public void handleMessage(Message m)
        {
            switch (m.what)
            {
            case MSG_ANIMATE:
                doAnimation();
                break;
            }
        }
    }
}
